package de.lns.level;

import java.io.IOException;
import java.util.HashSet;
import java.util.Stack;

import de.lns.Player;
import de.lns.entities.Entity;
import de.lns.entities.Hitbox;
import de.lns.entities.mobs.Mob;
import de.lns.entities.mobs.Orc;
import de.lns.entities.mobs.Troll;
import de.lns.entities.mobs.Gnome;
import de.lns.entities.turrets.Bullet;
import de.lns.entities.turrets.ImplosionShotTurret;
import de.lns.entities.turrets.Turret;
import de.lns.tick.GameState;
import de.lns.tick.UpdateThread;

/**
 * 
 * @author Frederick Hastedt
 *
 */
public class Level {
	
	
	/*
	 * entityList: list of all entities on the map
	 * mobList: list of all mobs on the map
	 * turretList: list of all turrets on the map
	 * bulletList: list of all bullets on the map
	 * removeStack: stack of entities that will be removed in this tick
	 * entityStack: stack of entities that will be added in this tick
	 * map: map object
	 * active: singleton of the map
	 */
	private static HashSet<Entity> entityList = new HashSet<Entity>();
	private static HashSet<Mob> mobList = new HashSet<Mob>();
	private static HashSet<Turret> turretList = new HashSet<Turret>();
	private static HashSet<Bullet> bulletList = new HashSet<Bullet>();
	private static Stack<Entity> removeStack = new Stack<Entity>();
	private static Stack<Entity> entityStack = new Stack<Entity>();
	private static Map map;
	private static Level active;
	
	/*
	 * i: iterator for waves
	 * spawned: checks if a wave is already spawned
	 * spawnDelay: standard spawnDelay
	 * lastSpawn: time when the last mob spawned 
	 */
	private static int lastSpawn=0;
	private static int spawnDelay = 500;
	private static int i = 0;
	private static int waveMobCount = 0;
	
	/**
	 * creates the first Level object at the start of the game,
	 */
	static{
		try {
			active = new Level("data/maps/map1.ini");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * 
	 * @param propertiepath: path of the .ini file for the map.
	 * 
	 */
	private Level(String propertiepath) throws IOException{
		map = new Map(propertiepath);
		//entityList.add(new Implosionshotturret(50,280));
	}
	
	/**
	 * Level singleton
	 * @return active object
	 */
	public static Level active() {
		if(active == null)
			try{
				active = new Level("data/maps/map1.ini");
			}catch (IOException e) {
				UpdateThread.kill("Couldn't load Level", e);
			}
		return active;
	}
	
	/**
	 * 
	 * @return map object
	 */
	public static Map getMap(){
		return map;
	}
	
	/**
	 * 
	 * @return int array with X, Y and Z coordinates of the spawnpoint
	 */
	public static int[] getSpawnpoint(){
		return map.getSpawnpoint();
	}
	
	/**
	 * 
	 * @return int array with X, Y and Z coordinates of the destination
	 */
	public static int[] getDestination(){
		return map.getDestination();
	}
	
	/**
	 * 
	 * @return HashSet<Shapes> with the clipObjects of the map
	 */
	public static HashSet<Shapes> getClipObjects(){
		return map.getClipObjects();
	}
	
	/**
	 * 
	 * @return HashSet<Shapes> with the noClipObjects of the map
	 */
	public static HashSet<Shapes> getNoClipObjects(){
		return map.getNoClipObjects();
	}
	
	/**
	 * 
	 * @return  int array with X, Y and Z dimensions of the map
	 */
	public static int[] getMapDimensions(){
		return map.getMapDimensions();
	}
	
	/**
	 * puts
	 * @param entity
	 * into the removeStack. All entities contained in it will be removed at the end of the tick.
	 */
	public static void removeEntity(Entity entity){
		removeStack.add(entity);
	}
	
	/**
	 * removes all entities contained in removeStack
	 * adds all entities contained in entityStack
	 */
	private void modifyEntities(){
		
		while(!removeStack.isEmpty()){
			Entity e = removeStack.pop();
			//Notify the Entity
			e.dispose();
			//Remove Entity from the the list of all Entities.
			entityList.remove(e);
			//Remove Entity from its respective list.
			if(e instanceof Bullet){
				bulletList.remove((Bullet) e);
			}else if(e instanceof Mob){
				mobList.remove((Mob) e);
			}else if(e instanceof Turret){
				turretList.remove((Turret) e);
			}
		}
		while(!entityStack.isEmpty()){
			Entity e = entityStack.pop();
			//Notify the Entity
			e.create();
			//Add Entity to the the list of all Entities.
			entityList.add(e);
			//Add Entity to its respective list.
			if(e instanceof Bullet){
				bulletList.add((Bullet) e);
			}else if(e instanceof Mob){
				mobList.add((Mob) e);
			}else if(e instanceof Turret){
				turretList.add((Turret) e);
			}
		}
	}
	/**
	 * 
	 * @return entityList
	 */
	public static HashSet<Entity> getEntityList(){
		return entityList;
	}
	
	/**
	 * spawns Mobs in regular waves
	 */	
	public static void spawnWaveMob(){
		
		if(i+3 <= map.getWaves().length){
			if(GameState.active().time() > map.getWaves()[i+2] && checkSpawnAllowed() && waveMobCount < map.getWaves()[i]){
				spawnMob(map.getWaves()[i+1]);
				waveMobCount++;
			}
			else if(waveMobCount == map.getWaves()[i]){
				waveMobCount = 0;
				i += 3;
			}		
		}
		
	}
	
	/**
	 * spawns a single mob
	 * @param mobID is the mob type
	 */
	public static void spawnMob(int mobID){
		switch(mobID){
		case 0:
			entityStack.add(new Orc(map.getSpawnpoint()[0], map.getSpawnpoint()[1], map.getPath()));
			break;
		case 1:
			entityStack.add(new Troll(map.getSpawnpoint()[0], map.getSpawnpoint()[1], map.getPath()));
			break;
		case 2:
			entityStack.add(new Gnome(map.getSpawnpoint()[0], map.getSpawnpoint()[1], map.getPath()));
			break;
		}
		lastSpawn = (int)GameState.active().time();
	}
	
	/**
	 * prevents mobs from being spawned faster than the spawnDelay
	 * @return
	 */
	public static boolean checkSpawnAllowed(){
		if(GameState.active().time() - lastSpawn >= spawnDelay)
			return true;
		else
			return false;
	}
	
	/**
	 * 
	 * @return mobList
	 */
	public static HashSet<Mob> getMobs(){		
		return mobList;		
	}
	
	/**
	 * 
	 * @return turretList
	 */
	public static HashSet<Turret> getTurrets(){		
		return turretList;		
	}
	
	/**
	 * 
	 * @return bulletList
	 */
	public static HashSet<Bullet> getBullets(){
		return bulletList;
		
	}
	
	/**
	 * checks if a bullet hits a mob
	 */
	public static void checkHitboxes(){
		for (Bullet bullet:getBullets()){
			bullet.checkHit();
		}
	}
	
	/**
	 * checks if a turret can be placed at the at the cursor position
	 * @param turret the turret that is tried to place
	 * @return boolean
	 */
	public static boolean tryplaceTurret(Turret turret){
		for(Shapes i:getClipObjects()){
			if(Hitbox.checkhit(turret.getHitbox(), i)){
				return false;
			}
		}
		for(Turret i:getTurrets()){
			if (Hitbox.checkhit(turret.getHitbox(),i.getHitbox())){
				return false;
			}
		}
		if(Player.getInstance().getMoney()<turret.getPrice()){
			return false;
		}
		Player.getInstance().buy(turret.getPrice());
		addEntity(turret);
		return true;
	}
	
	/**
	 * 
	 * @param entity is added to entityStack
	 */
	public static void addEntity(Entity entity){
		entityStack.add(entity);
	}
	
	/**
	 * calls the methods when it's their turn
	 */
	public void update(){
		spawnWaveMob();
		for(Entity i:entityList){
			i.update();
		}
		modifyEntities();
		
		checkHitboxes();
		
	}
	
	
}